##########################################
##GUI for darrt
##  wFn:the weighting function for BPS search algorithm
##  loadDset: helper function to convert dataset csv into dataset dictionary, for search initialization
##  constraints: adds initial step to policy search that allows user to set specifications (ie: sex must
##        be separate) to hold true in all policies
##  executeranges: contraints() helper function
##  fieldEval: gives rating of each policy field's specifications, approx a 0-1 scale
##  Tri class: displays policies to user
##########################################


from Tkinter import *
import tkMessageBox, settings
from math import pi,sin,sqrt,cos,log



def fieldEval(policy,vocab,fields):
	#helper function for tri class, creates ratings
	from math import log,sqrt
	import numpy
	fieldCounts=[]
	currentIndex=0
	for fieldIndex in fields:
		#use a fairly normalized scale		
		total=len([keys for keys in vocab if keys[0]==fieldIndex])-1
		bitCount=0
		binNum=1
		binList=[]
		for index in range(total):
			if policy[currentIndex]:
				bitCount+=1
				binList.append(binNum)
				binNum=0
			currentIndex+=1
			binNum+=1  #will get the size of all bins, only used for zipcode
			#method for score calculation can be tweaked 
		if fields[fieldIndex]=='race':
			score=float(bitCount)/float(total) #10
		if fields[fieldIndex]=='sex':
			score=float(bitCount)/float(total) #10
		if fields[fieldIndex]=='age':
			score=float(bitCount)/float(total) #50
		if fields[fieldIndex]=='zip5':
			#score=float(bitCount)/float(total)
			#score=(log(score,2)+5)*2  #experiment with different log bases
			score=1./(numpy.average(numpy.array(binList))+numpy.std(numpy.array(binList))) #100  #takes size and spread of bin as rating
			#score=1./(numpy.average(numpy.array(binList))+numpy.std(numpy.array(binList)))

		fieldCounts.append(score*score)
	fieldCounts.append(sum(fieldCounts)/3)
	return fieldCounts

class Tri:
	"""Class for displaying policies to user and allowing real time search
	Controls: mouse button rotates model, arrow keys shifts up/down/left/right, 
	enter to zoom in, shift enter to zoom out (NOTE: zoom still very buggy)"""

	def __init__(self, master,solutionPolicies,vocab,fields):
		"""Creates 3D interface for policy display"""
		
		
		######CAMERA SETTINGS#############################
		self.focusPt=[0,0,150]   
		self.focusPtDEFAULT=[0,0,150]
		self.elevation=30./180*pi #if changed, also change restore default
		self.rotation=135./180*pi   
		self.distance=10
		##################################################
		self.vocab=vocab
		self.fields=fields
		self.solPols=solutionPolicies
		self.currentPtString=None
		self.pointList=[]  # keep track of point objects for scatter, before transforming
		self.centroid=[1000/2,700/2-90]
		self.centroidDEFAULT=[1000/2,700/2-90]
		#creates coordinates of right side up triange and combines with Coords to form hexagon
		self.master=master #Tk()
		self.canvas=Canvas(self.master,height=700,width=1000,background='goldenrod1') #where everything is displayed
		self.DEFAULTAREA=700*1000
		self.ratio=1
		self.canvas.grid(sticky=N+W)
		#creates vandy logo at top left of screen
		photo=PhotoImage(file=settings.pathToVandyLogo)
		self.photo=photo.subsample(2,2)
		imageRef=self.canvas.create_image(100,100,image=self.photo,tags='logo')	
		self.create_Legend()  #creates legend at bottom left
		self.popUpWindow1=None 
		self.popUpWindow2=None
		self.TT=None  ##zipcode pop up window text
		self.TTage=None
		self.initialized=False #so window only resized when clicked for first time
		#plots the policies
		#########this is the core code##########
		self.initGraph() #creates rotatable axes and points, contains camera settings
		#######################################
		 #uncomment to quickly click for canvas coords at mouse
		self.xyMouse=[]
		self.xyMouse=self.canvas.bind("<Button-1>",self.initMouse) #intitialize mouse position
		self.canvas.bind("<B1-Motion>",self.rotate)  #rotates camera
		self.canvas.bind("<Shift-B1-Motion>",self.rotateFine) #fine tunes camera rotation
		self.canvas.bind("<Return>",self.incDistance)
		self.canvas.bind("<Shift-Return>",self.decDistance)
		self.canvas.bind("<KeyPress>",self.moveModel) # allows user to shift model up/down/left/right
		#self.canvas.bind("<Button-1>",self.findCoord)
		self.master.bind("<Configure>",self.resize)
		self.bugfix=True # for issue with configure bind being called at the rong time
		
		self.canvas.focus_set()  #gives canvas focus for moveModel bind
		master.mainloop()##
		
	

	def resize(self,event):	
		from math import fabs #note: del import settings
		if self.initialized:
			newArea=event.height*event.width
			if fabs(self.ratio-sqrt(float(newArea)/self.DEFAULTAREA))>.1:
				return #note: fix this. configure bind is called at wrong times, not just when resizeing corner
			self.ratio=sqrt(float(newArea)/self.DEFAULTAREA)
			self.centroid[0]=event.width/2
			self.centroid[1]=event.height/2-90*self.ratio
			for ptIndex in range(4):
				for coordIndex in range(3):
					self.axPts[ptIndex][coordIndex]=self.axPtsDEFAULT[ptIndex][coordIndex]*self.ratio
			#self.canvas.itemconfig('logo',image=PhotoImage(file=settings.pathToVandyLogo))
			self.canvas.delete('window1')
			if not self.currentPtString==None:
				self.displayPt(event=None,ptString=self.currentPtString)
			"""
			buttonTxt=int(11*self.ratio+5)
			if buttonTxt<4:
				buttonTxt=4
			ptDisplay=Frame(height=int(100*self.ratio),width=int(self.ratio*100),background='white',relief='solid',borderwidth=3) #used when point is clicked 
			text1='Policy rating: '+'%.2f' % self.resizeRating
			L1=Label(ptDisplay,text=text1,font=('Helvetica',buttonTxt))
			L2=Label(ptDisplay,text='Print Policy:',font=('Helvetica',buttonTxt))
			L1.grid(sticky='n',columnspan=2)
			L2.grid(row=1,column=0,columnspan=2)
				#######################################

			B1=Button(ptDisplay,text='Close',command=lambda:self.closeWindow(ptString,defaultFill,defaultOutline),font=('Helvetica',buttonTxt)) #creates close btn
			#B2=Button(ptDisplay,text='Bits',command=lambda:self.printPolString(ptString)) #will print policy string
			B3=Button(ptDisplay,text='Open',command=lambda:self.pprintPol(ptString),font=('Helvetica',buttonTxt)) #will pretty print policy
			B1.grid(sticky='s',row=3,column=0,pady=5,columnspan=2)
			#B2.grid(sticky='e',row=2,column=0,pady=5,padx=5)
			B3.grid(sticky='w',row=2,column=1,pady=5,padx=5)		
			window=self.canvas.create_window(825*self.ratio,140*self.ratio,window=ptDisplay,tags='window1',anchor='n') #825
			"""
			self.updateTasks(resize=True)
			
			
				

	def findCoord(self,event):
		#for utility purposes
		print(event.x,event.y)



	def create_Legend(self):
		graphCenter=[60,640]
		self.graphCenterDEFAULT=[60,640]
		self.canvas.create_line(graphCenter,graphCenter[0],graphCenter[1]-30,dash=4,tags='one')	
		self.canvas.create_line(graphCenter,graphCenter[0]-30,graphCenter[1]+20,tags='two')
		self.canvas.create_line(graphCenter,graphCenter[0]+30,graphCenter[1]+20,width=2,tags='three')
		self.canvas.create_oval(graphCenter[0]+3, graphCenter[1]+3, graphCenter[0]-3, graphCenter[1]-3,fill='blue',outline='blue',tags='four')
		self.canvas.create_oval(graphCenter[0]+2,graphCenter[1]-30+2,graphCenter[0]-2,graphCenter[1]-30-2,fill='green',outline='green',tags='five')
		self.canvas.create_oval(graphCenter[0]-30+2,graphCenter[1]+20+2,graphCenter[0]-30-2,graphCenter[1]+20-2,fill='green',outline='green',tags='six')
		self.canvas.create_oval(graphCenter[0]+30+2,graphCenter[1]+20+2,graphCenter[0]+30-2,graphCenter[1]+20-2,fill='green',outline='green',tags='seven')
		self.canvas.create_text(graphCenter[0],graphCenter[1]+35,text='Proper Orientation',font=('Helvetica',10),tags='eight')
		legendCenter=[110,610]
		self.canvas.create_text(legendCenter,text='Gender Specified:',font=('Helvetica',10),anchor='w',tags='nine')
		self.canvas.create_text(legendCenter[0],legendCenter[1]+20,text='Gender Generalized:',font=('Helvetica',10),anchor='w',tags='ten')
		self.canvas.create_text(legendCenter[0],legendCenter[1]+40,text='Most Specific:',font=('Helvetica',10),anchor='w',tags='thirteen')
		self.canvas.create_text(legendCenter[0],legendCenter[1]+60,text='Most General:',font=('Helvetica',10),anchor='w',tags='fourteen')
		keyOval1=[200,610]
		keyOval2=[210,630]
		keyOval3=[190,650]
		keyOval4=[190,670]
		self.canvas.create_oval(keyOval1[0]+4,keyOval1[1]+4,keyOval1[0]-4,keyOval1[1]-4,fill='black',tags='eleven')
		self.canvas.create_oval(keyOval2[0]+4,keyOval2[1]+4,keyOval2[0]-4,keyOval2[1]-4,fill='white',outline='black',tags='twelve')
		self.canvas.create_oval(keyOval3[0]+4,keyOval3[1]+4,keyOval3[0]-4,keyOval3[1]-4,fill='green',outline='black',tags='fifteen')
		self.canvas.create_oval(keyOval4[0]+4,keyOval4[1]+4,keyOval4[0]-4,keyOval4[1]-4,fill='blue',outline='black',tags='sixteen')
		self.defB=Button(self.canvas,text='Default',command=self.restoreDefault,highlightbackground='goldenrod1',font=('Helvetica',14))
		self.defB.place(x=self.centroid[0]-50,y=self.centroid[1]+300)
		self.returnPol=Button(self.canvas,text='Return Policy',command=self.returnString,highlightbackground='goldenrod1',font=('Helvetica',14))
		self.returnPol.place(x=self.centroid[0]-67,y=self.centroid[1]+330)

	def returnString(self):
		if self.prevPt==[]:
			print "dont do that"
		else:
			
			print self.policyLookup[int(self.prevPt[0])]



	def update_Legend(self,x,y):
		#changes legend based on resize	
		graphCenter=[self.graphCenterDEFAULT[0]*self.ratio,self.graphCenterDEFAULT[1]*self.ratio]
		legendCenter=[110*self.ratio,610*self.ratio]
		keyOval1=[200*self.ratio,610*self.ratio]
		keyOval2=[210*self.ratio,630*self.ratio]
		keyOval3=[190*self.ratio,650*self.ratio]
		keyOval4=[190*self.ratio,670*self.ratio]
		buttonTxt=int(11*self.ratio+5)
		if buttonTxt<4:
			buttonTxt=4
		self.defB.config(font=('Helvetica',buttonTxt))
		self.returnPol.config(font=('Helvetica',buttonTxt))
		self.defB.place(x=self.centroid[0]-50*self.ratio,y=self.centroid[1]+300*self.ratio)
		self.returnPol.place(x=self.centroid[0]-50*self.ratio-17,y=self.centroid[1]+300*self.ratio+30)
		self.canvas.coords('one',graphCenter[0],graphCenter[1],graphCenter[0],graphCenter[1]-30*self.ratio)
		self.canvas.coords('two',graphCenter[0],graphCenter[1],graphCenter[0]-30*self.ratio,graphCenter[1]+20*self.ratio)
		self.canvas.coords('three',graphCenter[0],graphCenter[1],graphCenter[0]+30*self.ratio,graphCenter[1]+20*self.ratio)
		R=int(3*self.ratio+.5)
		self.canvas.coords('four',graphCenter[0]+R, graphCenter[1]+R, graphCenter[0]-R, graphCenter[1]-R)
		self.canvas.coords('five',graphCenter[0]+R,graphCenter[1]-30*self.ratio+R,graphCenter[0]-R,graphCenter[1]-30*self.ratio-R)
		self.canvas.coords('six',graphCenter[0]-30*self.ratio+R,graphCenter[1]+20*self.ratio+R,graphCenter[0]-30*self.ratio-R,graphCenter[1]+20*self.ratio-R)
		self.canvas.coords('seven',graphCenter[0]+30*self.ratio+R,graphCenter[1]+20*self.ratio+R,graphCenter[0]+30*self.ratio-R,graphCenter[1]+20*self.ratio-R)
		self.canvas.coords('eight',graphCenter[0],graphCenter[1]+35*self.ratio)	
		self.canvas.coords('nine',legendCenter[0],legendCenter[1])
		self.canvas.coords('ten',legendCenter[0],legendCenter[1]+20*self.ratio)
		self.canvas.coords('eleven',keyOval1[0]+R,keyOval1[1]+R,keyOval1[0]-R,keyOval1[1]-R)
		self.canvas.coords('twelve',keyOval2[0]+R,keyOval2[1]+R,keyOval2[0]-R,keyOval2[1]-R)
		self.canvas.coords('thirteen',legendCenter[0],legendCenter[1]+40*self.ratio)
		self.canvas.coords('fourteen',legendCenter[0],legendCenter[1]+60*self.ratio)
		self.canvas.coords('fifteen',keyOval3[0]+R,keyOval3[1]+R,keyOval3[0]-R,keyOval3[1]-R)
		self.canvas.coords('sixteen',keyOval4[0]+R,keyOval4[1]+R,keyOval4[0]-R,keyOval4[1]-R)

		if buttonTxt>11:
			self.canvas.itemconfig('eight',font=('Helvetica',buttonTxt-6))
			self.canvas.itemconfig('eight',text='Proper Orientation')
			self.canvas.itemconfig('nine',font=('Helvetica',buttonTxt-6))
			self.canvas.itemconfig('nine',text='Gender Specified')
			self.canvas.itemconfig('ten',font=('Helvetica',buttonTxt-6))
			self.canvas.itemconfig('ten',text='Gender Generalized')
			self.canvas.itemconfig('thirteen',text='Most Specific')
			self.canvas.itemconfig('thirteen',font=('Helvetica',buttonTxt-6))
			self.canvas.itemconfig('fourteen',text='Most General')
			self.canvas.itemconfig('fourteen',font=('Helvetica',buttonTxt-6))
			
		else:	
			self.canvas.coords('nine',legendCenter[0]+20,legendCenter[1])
			self.canvas.coords('ten',legendCenter[0]+20,legendCenter[1]+20*self.ratio)
			self.canvas.coords('thirteen',legendCenter[0]+20,legendCenter[1]+40*self.ratio)
			self.canvas.coords('fourteen',legendCenter[0]+20,legendCenter[1]+60*self.ratio)
			
			self.canvas.itemconfig('eight',text='P.O.')
			self.canvas.itemconfig('nine',text='G.S.')
			self.canvas.itemconfig('ten',text='G.G.')
			self.canvas.itemconfig('thirteen',text='M.S.')
			self.canvas.itemconfig('fourteen',text='M.G.')
			

	def restoreDefault(self):
		#reorients graph back to factory settings
		self.elevation=30./180*pi 
		self.rotation=135./180*pi   
		self.distance=10
		self.updateTasks()


	def displayPt(self,event,ptString):	
		"""displays the features of the point clicked, ie: policy string"""
		if self.currentPtString==None and event==None:
			return
		buttonTxt=int(11*self.ratio+5)
		if buttonTxt<4:
			buttonTxt=4
		self.currentPtString=ptString
		self.bugfix=False
		#self.updateOverlap(debug=False)  #set to true for debuging
		#print ptString #debug 
		#print(self.scatDepth[self.ptObjs.index(int(ptString))]) #comment out w/not debug
		#print fieldEval(self.policyLookup[int(ptString)],self.vocab,self.fields)
		#print('\n')
		#NOTE:no compatibility with 'x-ing out' of pop up windows
		#ptString is identifier for point in self.ptObjs, --which point was clicked--

		#self.prevPt is a list of [object id,defaultFill,defaultOutline] from last call to this function
		if not self.prevPt==[]: 
			#if self.prevPt==[], then this is the first pt clicked, or after close button
			self.closeWindow(self.prevPt[0],self.prevPt[1],self.prevPt[2],closePops=False)
		if not self.popUpWindow1==None:
			self.printPolString(ptString)  #should stay up if another point clicked
		if not self.popUpWindow2==None:  #if popupwindow is already up when point clicked, then update popup
			self.pprintPol(ptString)	
		if not self.TT==None:   #zipwindow is already up
			self.createZipWd(self.policyLookup[int(ptString)],update=True)
		if not self.TTage==None:
			self.createAgeWd(self.policyLookup[int(ptString)],update=True)
		configDict=self.canvas.itemconfig(ptString) #to access default colors
		defaultFill=configDict['fill'][4]
		defaultOutline=configDict['outline'][4]
		self.canvas.itemconfig(ptString,fill='red',outline='black') #to simulate staying active until window close
		ptDisplay=Frame(height=100*self.ratio,width=100*self.ratio,background='white',relief='solid',borderwidth=3) #used when point is clicked
		######what the window will say#########
		rating=self.ratingLookup[int(ptString)]
		self.resizeRating=rating
		text1='Policy rating: '+'%.2f' % rating
		L1=Label(ptDisplay,text=text1,font=('Helvetica',buttonTxt))
		L2=Label(ptDisplay,text='Print Policy:',font=('Helvetica',buttonTxt))
		L1.grid(sticky='n',columnspan=2)
		L2.grid(row=1,column=0,columnspan=2)
		#######################################
		B1=Button(ptDisplay,text='Close',command=lambda:self.closeWindow(ptString,defaultFill,defaultOutline),font=('Helvetica',13)) #creates close btn
		#B2=Button(ptDisplay,text='Bits',command=lambda:self.printPolString(ptString)) #will print policy string
		B3=Button(ptDisplay,text='Open',command=lambda:self.pprintPol(ptString),font=('Helvetica',13)) #will pretty print policy
		B1.grid(sticky='s',row=3,column=0,pady=5,columnspan=2)
		#B2.grid(sticky='e',row=2,column=0,pady=5,padx=5)
		B3.grid(sticky='w',row=2,column=1,pady=5,padx=5)
		window=self.canvas.create_window(825*self.ratio,140*self.ratio,window=ptDisplay,tags='window1',anchor='n') #825
		self.prevPt=[ptString,defaultFill,defaultOutline] #so color changes back in case another point clicked before close button 
		self.bugfix=True


	def updateOverlap(self,debug=False):
		#makes sure all points are overlapping correctly, corresponding to depth
		for ptString in self.ptObjs:
			orderedPts=[]
			#ptString=str(ptString)
			bbox=self.canvas.bbox(ptString)  #gives bounding box of point
			olapPts=self.canvas.find_overlapping(bbox[0],bbox[1],bbox[2],bbox[3])  #gives points the first one overlaps
			if debug:
				import pdb
				pdb.set_trace()
			if len(olapPts) > 1:   #if 1, means only point bbox was called on is there, skip
				olapPts=list(olapPts)   #so its mutable
				#remove overlapping points that arent scatter pts
				badPoints=[]
				for point in olapPts:
					if not (point in self.ptObjs):
						badPoints.append(point) #can't just remove, bad things happen with iter
				for point in badPoints:
					olapPts.remove(point)
				#continue
				orderedPts=[olapPts[0]]
				for pt in olapPts[1:]:    #this loop orders olapPts based on scapdepth, into orderedPts
				 if pt in self.ptObjs:
					Index=0
					loop=True
					while(loop):
						mydepth=self.scatDepth[self.ptObjs.index(int(pt))]  #depth of corresponding pt object  
						if Index==len(orderedPts):   #got to end, put pt at back of orderedPts
							orderedPts.append(pt)
							break   
					 	if mydepth<self.scatDepth[self.ptObjs.index(orderedPts[Index])]: 
							orderedPts.insert(Index,pt)
							loop=False
						Index+=1
				#now raise points baised on depth
				
				for pt in orderedPts:
					self.canvas.tag_raise(str(pt))   #should raise in order of less depth to more depth
						

			

		
	def printPolString(self,pointObject):
		#note: may not be used
		from policyEvaluation import PolicyHandler
		pH=PolicyHandler(self.vocab,self.fields)
		zipIndex=pH.fieldLookup['zip5']
		#opens new window, prints bits of policy array
		policy=self.policyLookup[int(pointObject)]
		#uses toplevel initiated in __init__	
		index=len(self.vocab)-len(self.fields)-len(pH.vocabLists[zipIndex])+1  #this is the index of the start of zip5
		text='Your policy bitarray is: \n \n Race, sex, and age: \n \n'+str(policy)[0:index]+'\n \n Zipcodes: \n \n'+str(policy)[index:]
		#note: change racesexage string to more compatible
		if self.popUpWindow1==None:  #only make once
			self.popUpWindow1=Toplevel(self.master,bd=3,relief='flat') #will be pop up window for displayPt method's printPolicy bind
			self.popUpWindow1.geometry('400x300+800-400') #see geometry strings in tkinter tutorial
			self.popUpWindow1.transient(self.canvas) #will always be on top
			self.v1=StringVar()
			self.v1.set(text)
			Label(self.popUpWindow1,textvariable=self.v1,wraplength=350,font=('Helvetica',12)).grid()
		else:
			self.v1.set(text)
			

	def pprintPol(self,pointObject):
		from policyEvaluation import PolicyConstructor
		#opens new window, prints specifications of policy
		pC=PolicyConstructor(self.vocab,self.fields)
		policy=self.policyLookup[int(pointObject)]
		text=str(pC.pprintPolicyReturn(policy))  #will display specifications
		if self.popUpWindow2==None:
			self.popUpWindow2=Toplevel(self.master,bd=3,relief='flat')
			self.popUpWindow2.geometry('400x650+880-50') #see geometry strings in tkinter tutorial
			self.popUpWindow2.transient(self.canvas)
			self.v2=StringVar()
			self.v2.set(text)
			Label(self.popUpWindow2,textvariable=self.v2,wraplength=350,font=('Helvetica',12)).grid(sticky=N,row=0)  #note: was pack
			self.createAgeWd(policy)
			self.createZipWd(policy)
			#Button(self.popUpWindow2,text='zipcodes',command=lambda: self.createZipWd(policy)).grid(sticky=N,row=2)  #note: was pack
			#Button(self.popUpWindow2,text='ages',command=lambda: self.createAgeWd(policy)).grid(sticky=N,row=1)  #note: was pack	
			
			
		else:
			self.v2.set(text)
		


	def closeWindow(self,ptString,defaultFill=None,defaultOutline=None,closePops=True):
		#from close button bind
		self.canvas.delete('window1')
		self.canvas.itemconfig(ptString,fill=defaultFill,outline=defaultOutline) #returns red pt back to default
		self.prevPt=[] 
		if closePops: #pop up windows should be closed and reinit
			if not self.popUpWindow1==None: 
				self.popUpWindow1.withdraw()
			if not self.popUpWindow2==None:
				self.popUpWindow2.withdraw()
			self.popUpWindow1=None
			self.popUpWindow2=None #so it can be reinitialized
			
			self.TT=None
			self.TTage=None
		 #deletes window displayed when point clicked, use window tag because
		#windows stack on top of each other, closeWindow closes all of them
		#note: function should be revised

	def createZipWd(self,policy,update=False):
		
	  from policyEvaluation import PolicyConstructor
          pC=PolicyConstructor(self.vocab,self.fields)
	  text=pC.pprintPolicyReturn(policy,printZip=True)
	  if self.TT==None:
		 #creates window to display zipcodes, just resizes second popup and adds scroll, toggle on and off
		
		self.frame=Frame(self.popUpWindow2)
		self.TT=Text(self.frame,height=20,width=50,background='white')
		scroll=Scrollbar(self.frame)
		scroll.config(command=self.TT.yview)
		self.TT.configure(yscrollcommand=scroll.set)
		self.TT.grid(row=2,column=0) #pack side left
		self.TT.insert('end',text)
		scroll.grid(row=2,column=1,sticky=N+S)  #pack side right 
		self.frame.grid()
		#note: disallow text editing, note: toggle on off is .grid_remove and recreate. inconsistent

	  else:
		if not update:     #shouldnt be updated, should be closed   
			#if self.TT not None, zip button clicked with window already showing, so remove it
			self.frame.grid_remove()
			self.TT=None
			self.popUpWindow2.geometry('400x300') #reset size
			#self.TT.delete('1.0','end')
			#self.TT.insert('end','hello friend') #note: text instead
		else:
			self.TT.delete('1.0','end')
			self.TT.insert('end',text) #note: text instead
		
	

	def createAgeWd(self,policy,update=False):
	  from policyEvaluation import PolicyConstructor
          pC=PolicyConstructor(self.vocab,self.fields)
	  text=pC.pprintPolicyReturn(policy,printAge=True)
	  if self.TTage==None:
		 #creates window to display ages, just resizes second popup and adds scroll, toggle on and off
		
		self.frameage=Frame(self.popUpWindow2)
		self.TTage=Text(self.frameage,height=20,width=50,background='white')
		scroll=Scrollbar(self.frameage)
		scroll.config(command=self.TTage.yview)
		self.TTage.configure(yscrollcommand=scroll.set)
		self.TTage.grid(row=2,column=0) #pack side left
		self.TTage.insert('end',text)
		scroll.grid(row=2,column=1,sticky=N+S)  #pack side right 
		self.frameage.grid()
		#note: disallow text editing, note: toggle on off is .grid_remove and recreate. inconsistent

	  else:
		if not update:     #shouldnt be updated, should be closed   
			#if self.TT not None, zip button clicked with window already showing, so remove it
			self.frameage.grid_remove()
			self.TTage=None
			self.popUpWindow2.geometry('400x300') #reset size
			#self.TTage.delete('1.0','end')
			#self.TTage.insert('end','hello friend') #note: text instead
		else:
			self.TTage.delete('1.0','end')
			self.TTage.insert('end',text) #note: text instead
	

	def moveModel(self,event):	
		"""command=arrow keys"""
		#allows model to be moved around canvas by arrow keys, scale can be changed as needed
		coords=self.canvas.coords(self.text[2])
		SCALE=30
		if event.keysym=='Up':
			self.centroid[1]-=SCALE
			self.canvas.coords(self.text[2],coords[0],coords[1]-SCALE)
		if event.keysym=='Down':
			self.centroid[1]+=SCALE
			self.canvas.coords(self.text[2],coords[0],coords[1]+SCALE)
		if event.keysym=='Right':
			self.centroid[0]+=SCALE
			self.canvas.coords(self.text[2],coords[0]+SCALE,coords[1])
		if event.keysym=='Left':
			self.centroid[0]-=SCALE
			self.canvas.coords(self.text[2],coords[0]-SCALE,coords[1])
		self.updateTasks()
		



	def incDistance(self,event):
		"""command=return"""
		self.distance+=50
		self.updateTasks()


	def decDistance(self,event):
		"""command=shift-return"""
		self.distance-=50
		self.updateTasks()


	def initMouse(self,event): 
		"""command=mouse click"""
		#sets initial position for rotate()
		self.initialized=True
		return [event.x,event.y]


	def rotate(self,event):
		"""command=mouse motion"""
		#rotates camera based on mouse position, click before motion initializes self.xyMouse
		#next motion causes rotation
		if self.xyMouse[0] < event.x:
			self.rotation-=.05
		if self.xyMouse[0] > event.x:
			self.rotation+=.05
		if self.xyMouse[1] > event.y:
			self.elevation-=.05
		if self.xyMouse[1] < event.y:
			self.elevation+=.05
		self.updateTasks()
		self.xyMouse=[event.x,event.y]


	def rotateFine(self,event):
		#command=shift-mouse motion
		if self.xyMouse[0] < event.x:
			self.rotation-=.005
		if self.xyMouse[0] > event.x:
			self.rotation+=.005
		if self.xyMouse[1] > event.y:
			self.elevation-=.005
		if self.xyMouse[1] < event.y:
			self.elevation+=.005
		self.updateTasks()
		self.xyMouse=[event.x,event.y]

	
	
	def updateTasks(self,resize=False):
  		"""redraws points,axes, and text when rotated, etc"""
	  
		ptsTrans=[]	
		
		for a in range(3):
			self.focusPt[a]=self.focusPtDEFAULT[a]*self.ratio
		####this section changes axes####
		for pt in self.axPts:
			newPt=self.devshedCalc(pt,self.focusPt)
			newPt[0]+=self.centroid[0]
			newPt[1]=self.centroid[1]-newPt[1]
			ptsTrans.append(newPt)
		for a in range(3):
			self.canvas.coords(self.axes[a],ptsTrans[0][0],ptsTrans[0][1],ptsTrans[a+1][0],ptsTrans[a+1][1])
		####this section rotates scatter points####
		index=0
		self.scatDepth=[] #reinitialize with devshedCalc, below
		RMAX=5 
		#changes the pt depth
		if resize:
			self.update_Legend(self.centroid[0],self.centroid[1])
			for a in range(len(self.scatPts)):
				spt=self.scatPts[a]
				for coord in range(3):
					spt[coord]=self.scatPtsDEFAULT[a][coord]*self.ratio
			
		for pt in self.scatPts:
			newPt=self.devshedCalc(pt,self.focusPt)
			newPt[0]+=self.centroid[0]
			newPt[1]=self.centroid[1]-newPt[1]
			#self.scatDepth has been updated, use for resizing points
			R=(float(self.scatDepth[index])/310*RMAX+1+RMAX)*self.ratio
			self.canvas.coords(self.ptObjs[index],newPt[0]+R,newPt[1]+R,newPt[0]-R,newPt[1]-R)
			index+=1
		####this section cause text labels to follow####
		self.setText(self.axPts[1],self.axPts[2])
		"""self.canvas.coords(self.text[0],ptsTrans[1][0],ptsTrans[1][1])	
		self.canvas.coords(self.text[1],ptsTrans[2][0],ptsTrans[2][1])
		self.canvas.coords(self.text[2],ptsTrans[3][0],ptsTrans[3][1]) # I don't like how the animation looks for 'ZIPCODE'"""
		self.updateOverlap()  #so points overlap corresponding to depth
		

	
	def initGraph(self):
		from math import pi
		"""focus point is direction camera points in, rotation spins about z axis (looks like righthand sys), 
		elevation spins about NEW camera x-axis, easiest to think about in that order.  
		focal point, distance is out of y. By default--all values zero--, y=out, x=right, z=up.
		depeding on focus point,  x axis rotation depends on camera, but z axis rotation is absolute"""
		#trying new way to project points
		#keep in mind y axis increases down screen, some math may be negative to counteract this
		self.axes=[] #keeps track of axis objects
		axPtsTrans=[] #2d points after projection, this is what actually goes on screen
		#initialize axes, call setPts to init points, then init text
		self.scaleDist=300 #upper bound of point coord
		axPt0=[0,0,0] #origin
		axPt1=[300,0,0] #X axis
		axPt2=[0,300,0] #Y axis
		axPt3=[0,0,300] #Z axis
		axPt0D=[0,0,0] 
		axPt1D=[300,0,0] 
		axPt2D=[0,300,0] 
		axPt3D=[0,0,300]
		self.axPtsDEFAULT=[axPt0D,axPt1D,axPt2D,axPt3D]  #constant
		self.axPts=[axPt0,axPt1,axPt2,axPt3]
		self.scatDepth=[] #will correspond with self.scatPts, keeps track of "out of screen" coord
		self.text=None #will be initialized by self.setText
		for pt in self.axPts:
			newPt=self.devshedCalc(pt,self.focusPt)
			newPt[0]+=self.centroid[0]
			newPt[1]=self.centroid[1]-newPt[1]
			axPtsTrans.append(newPt)
		self.zipTextDEFAULT=self.devshedCalc(self.axPts[3],self.focusPt)  #for resize setText
		for a in range(3):
			#keeps track of all projected points
			self.axes.append(self.canvas.create_line(axPtsTrans[0],axPtsTrans[a+1]))
		self.canvas.itemconfig(self.axes[0],width=1)
		self.canvas.itemconfig(self.axes[1],width=2)
		self.canvas.itemconfig(self.axes[2],dash=4)
		#self.canvas.itemconfig(self.axes[1],fill='red') #change back for axis color
		self.scatDepth=[] #reinitialize
		self.setPts() #plots scatter points	
		self.setText(axPt1,axPt2,axPtsTrans)  #sets the text at end of axes
				

	

	def setPts(self):
		from random import randint
		"""plots 3d scatter points"""
		self.scatPts=[]
		self.scatPtsDEFAULT=[]
		for policy in self.solPols:
			polPt=self.genPolicyPt(policy,self.vocab,self.fields)
			polPt2=self.genPolicyPt(policy,self.vocab,self.fields)
			self.scatPtsDEFAULT.append(polPt2)
			self.scatPts.append(polPt)
		"""###########uncomment for random point demo#################
		for a in range(50):
			pt=[randint(0,300),randint(0,300),randint(0,300)]
			pt.append(randint(1,30)) #random overall rating
			if randint(0,5)==3:
				pt.append(False)
			else:
				pt.append(True)   
			self.scatPts.append(pt)
		##############################################################"""
		RMAX=5 #limit for point size
		self.ptObjs=[]	
		self.policyLookup={} #will be dictionary connection point object to particular policy
		self.ratingLookup={} #will take a point object and output its overall rating
		index=0
		for pt in self.scatPts:	
			newPt=self.devshedCalc(pt, self.focusPt)
			newPt[0]+=self.centroid[0]
			newPt[1]=self.centroid[1]-newPt[1]
			C=newPt
			#self.scatDepth has been initialized, use for radius of points
			R=(float(self.scatDepth[index])/310*RMAX+1+RMAX) #changes point size to simulate depth
			fill='black'
			outline='white'
			#note: will later be changed to tell when male or female
			if not pt[4]:
				#5th value in pt is boolean for sex identified or not
				fill='white'
				outline='black'
			self.ptObjs.append(self.canvas.create_oval(C[0]+R,C[1]+R,C[0]-R,C[1]-R,fill=fill,width=1,activefill='red',activeoutline='black',outline=outline))
			self.policyLookup[self.ptObjs[index]]=self.solPols[index]
			self.ratingLookup[self.ptObjs[index]]=pt[3] #pt[3] was stored as overall rating
			index+=1
		self.prevPt=[] #used in displayPt method
		for obj in self.ptObjs:  #add clickableness to points
			self.canvas.tag_bind(obj,"<Button-1>",lambda event,arg=str(obj):self.displayPt(event,arg)) 
			#just like bind, but with lambda can pass additional argument: obj to keep track of which pt clicked
		self.updateOverlap()    #makes sure points overlap correctly corresponding to depth
		



	def setText(self,axPt1,axPt2,axPtsTrans=None):  #axPtsTrans just for z axis text
		#change axPts slightly to not clash with age and race text ie. x and y axis
		a1x=axPt1[0]+20
		a2y=axPt2[1]+20
		#note: consider revising axPt math
		a1=[a1x,axPt1[1],axPt1[2]]
		a2=[axPt2[0],a2y,axPt2[2]]
	        #note, could call PAD for settings
		#point that ended x axis now is longer, to be end of text 'AGE'
		textPt1=self.devshedCalc(a1,self.focusPt)
		textPt1[0]+=self.centroid[0]
		textPt1[1]=self.centroid[1]-textPt1[1]
		textPt2=self.devshedCalc(a2,self.focusPt)
		textPt2[0]+=self.centroid[0]
		textPt2[1]=self.centroid[1]-textPt2[1]
		if self.text is None:  #this means first initialization
			self.text=[self.canvas.create_text(textPt1,text='RACE')]
			self.text.append(self.canvas.create_text(textPt2,text='AGE'))
			self.text.append(self.canvas.create_text(axPtsTrans[3][0],axPtsTrans[3][1]-50,text='ZIPCODE'))
			
		else:   
			#this means text has already been displayed, and needs to be rotated, (setText called by rotate())
			self.canvas.coords(self.text[0],textPt1[0],textPt1[1])
			self.canvas.coords(self.text[1],textPt2[0],textPt2[1])
			zPt=[0,0,300*self.ratio] #note, revise calculation
			newPoint=self.devshedCalc(zPt,self.focusPt)
			x=newPoint[0]+self.centroid[0]
			y=self.centroid[1]-newPoint[1]
			offset=50*self.ratio
			self.canvas.coords(self.text[2],x,y-offset)
			fSize=int(round(12*sqrt(self.ratio)+.5)) #adjust font size, round up
			for id in self.text:
				self.canvas.itemconfig(id,font=('Helvetica',fSize))
			


	def devshedCalc(self,pt,focus):
		"""for setPts(), conv 3d->2d default camera looks down y axis, transforms simple 3d points to 3d
		points relative to camera angle, then to points that can be displayed in 2d"""
		from math import cos,sin
		from numpy import matrix
		elevation=self.elevation
		rotation=self.rotation
		distance=self.distance
		pt=matrix(str(pt[0])+';'+str(pt[1])+';'+str(pt[2]))
		focus=matrix(str(focus[0])+';'+str(focus[1])+';'+str(focus[2]))
		a1=pt-focus
		#now the rotation matrices
		M1=matrix('1,0,0;'+'0,'+str(cos(-elevation))+','+str(sin(elevation))+';'+'0,'+str(-sin(-elevation))+','+str(cos(elevation))) 
		M2=matrix(str(cos(rotation))+','+str(sin(rotation))+',0;'+str(-sin(rotation))+','+str(cos(rotation))+',0;0,0,1')
		a2=M1*M2*a1
		a3=[a2[0,0],a2[1,0]-distance,a2[2,0]] #note: if realistic zoom needed, change +distance to -distance
		px=a3[0]*(1500-a3[1])/1500
		py=a3[2]*(1500-a3[1])/1500
		self.scatDepth.append(-a2[1,0]+distance) #for resizing points, only negative because screen y axis is inverted
		return [px,py]



	def genPolicyPt(self,policy,vocab,fields):
		"""takes in a solution policy, outputs 3d coordinates of point in solution space """
		#the higher the scale, the less it affect position
		RACESCALE=100 #effectively max value
		AGESCALE=250  #ef max value
		ZIPSCALE=300  #ef max value
		assert fields[0].upper()=='RACE'
		assert fields[2].upper()=='AGE'
		assert fields[3].upper()=='ZIP5'
		ratings=fieldEval(policy,vocab,fields) #gives [race rating,sex rating,age rating, zip5 rating, overall]
		#note: make more compatible, with different ordered fields
		raceDist=float(ratings[0])/RACESCALE*self.scaleDist*200 #mem
		if raceDist>self.scaleDist:
			raceDist=self.scaleDist
		ageDist=float(ratings[2])*self.scaleDist*10
		if ageDist>self.scaleDist:
			ageDist=self.scaleDist
		zipDist=float(ratings[3])*self.scaleDist*50	
		if zipDist>self.scaleDist:
			zipDist=self.scaleDist
		newPt=[raceDist,ageDist,zipDist,(raceDist+ageDist+zipDist)/900] #keep track of point lcoation(x,y,z) and overall rating (w)
		if ratings[1]==1:  #note: changed because square
			newPt.append(True)  #sex is identified
		if ratings[1]==0:
			newPt.append(False)  #sex is not identified
		
		return newPt
	








